今天,我們來聊聊 Rust 的錯誤處理機制。Rust 的錯誤處理設計旨在幫助開發者寫出更加穩健和可靠的程式。
Rust 將錯誤分為可恢復和不可恢復兩種。對於可恢復錯誤,Rust 提供了 Result<T, E>
枚舉:
enum Result<T, E> {
Ok(T),
Err(E),
}
使用 Result
的例子:
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("無法打開文件:{:?}", error)
},
};
}
?
運算符號可以大大簡化錯誤處理的程式碼:
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
我們可以建立自己的錯誤類型來更好地表達程式中可能出現的錯誤:
use std::fmt;
#[derive(Debug)]
struct AppError {
kind: String,
message: String,
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.kind, self.message)
}
}
impl std::error::Error for AppError {}
thiserror
lib可以幫助我們更容易地定義錯誤類型:
use thiserror::Error;
#[derive(Error, Debug)]
enum DataStoreError {
#[error("找不到資料")]
NotFound,
#[error("無訪問權限")]
Unauthorized,
#[error("無效的輸入:{0}")]
InvalidInput(String),
}
有時我們需要將一種錯誤類型轉換為另一種:
use std::fs;
use std::io;
fn read_username_from_file() -> Result<String, io::Error> {
fs::read_to_string("hello.txt")
}
fn get_username() -> Result<String, String> {
read_username_from_file().map_err(|e| e.to_string())
}
對於不可恢復的錯誤,Rust 提供了 panic!
:
fn main() {
panic!("發生了嚴重錯誤!");
}
unwrap
和 expect
方法可以在錯誤發生時快速 panic:
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
let f = File::open("hello.txt").expect("無法打開文件");
}
讓我們看一個更複雜的例子,看看如何在實際應用中處理錯誤:
Cargo.toml
[dependencies]
rand = "0.8.5"
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0.63"
serde_json = "1.0.128"
main.rs
use std::fs::File;
use std::io::{self, Read};
use serde::Deserialize;
use thiserror::Error;
#[derive(Deserialize, Debug)]
struct Config {
server: String,
port: u16,
// 其他...
}
#[derive(Error, Debug)]
enum ConfigError {
#[error("無法讀取設定文件")]
ReadError(#[from] io::Error),
#[error("設定文件格式無效")]
ParseError(#[from] serde_json::Error),
}
fn read_config(path: &str) -> Result<Config, ConfigError> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let config: Config = serde_json::from_str(&contents)?;
Ok(config)
}
fn main() {
match read_config("config.json") {
Ok(config) => println!("成功讀取設定:{:?}", config),
Err(e) => eprintln!("讀取設定失敗:{}", e),
}
}
讀取失敗
新增 config.json
{
"server": "localhost",
"port": 25525
}
讀取成功
明天,我們來探討 Rust 的泛型和 trait,這些概念允許我們編寫更加靈活和可重用的程式。